package com.example.dbsample.config.database;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.example.dbsample.config.business.BaseSwitchConfig;
import com.example.dbsample.exception.BusinessException;
import com.example.dbsample.model.constants.DataSourceConstants;
import com.example.dbsample.util.worker.CachedUidGenerator;
import com.example.dbsample.util.worker.RandomWorkerIdAssigner;
import com.example.dbsample.util.worker.WorkerIdAssigner;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.sql.SQLException;

//@Configuration
@Slf4j
@ConditionalOnMissingBean(value = PgAutoConfiguration.class, name = "pgAutoConfiguration") // 这个无实际意义，只是为了说明用法
@MapperScan(basePackages = {"com.example.*.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory")
@ConditionalOnBean(value = {DataSourceConfig.class}, name = {"dataSourceConfig"})          // 这个无实际意义，只是为了说明用法
public class PgAutoConfiguration {
    @Autowired(required = false)
    private WorkerIdAssigner workerIdAssigner;

    @Autowired
    private BaseSwitchConfig baseSwitchConfig;

    @Bean
    public DataSourceConfig getDataSourceConfig() {
        return new DataSourceConfig();
    }

    @Autowired
    private DataSourceConfig dataSourceConfig;

    @Autowired
    MybatisPlusInterceptor mybatisPlusInterceptor;

    /**
     * 用完即弃的WorkerIdAssigner，依赖DB操作
     */
    @Bean
    public CachedUidGenerator cachedUidGenerator() {
        CachedUidGenerator cachedUidGenerator = new CachedUidGenerator();
        if(workerIdAssigner == null) {
            if(baseSwitchConfig.isEnableRadomWorkId()) {
                workerIdAssigner = new RandomWorkerIdAssigner();
            } else {
                throw new BusinessException("请实现WorkerIdAssigner");
            }
        }

        cachedUidGenerator.setWorkerIdAssigner((workerIdAssigner));

        // 以下为可选配置，入围知道将采用默认值
        cachedUidGenerator.setTimeBits(29);
        cachedUidGenerator.setWorkerBits(21);
        cachedUidGenerator.setSeqBits(13);
        cachedUidGenerator.setEpochStr("2023-04-07");

        // RingBuffer size扩容参数，可提高UID生成的吞吐量
        // 默认：3，院bufferSize=8192，扩容后bufferSize=8192 << 3 = 65536
        cachedUidGenerator.setBoostPower(3);

        /**
         * 指定核酸想RingBuffer中扩充UID，取值为百分比（0, 100）,默认为50
         * 举例：bufferSize=1024，paddingFactor=50 -> threshold=1024 * 50 / 100 = 512
         * 当换上可用UID数量 < 512时，将自动对RingBuffer进行填充补全
         * 另外一种RingBuffer填充时机，在Schedule线程中，周期性检查填充
         * 默认：不配置此项，即不使用Schedule线程，如需使用，请指定Schedule线程时间间隔，单位：秒
         */
        // cachedUidGenerator.setScheduleInterval(60L);

        return cachedUidGenerator;
    }

    @Bean(value = "dataSource")
    @Primary
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(dataSourceConfig.getUrl());
        druidDataSource.setUsername(dataSourceConfig.getUsername());
        druidDataSource.setPassword(dataSourceConfig.getPassword());
        druidDataSource.setDriverClassName(dataSourceConfig.getDriverClassName());
        druidDataSource.setInitialSize(dataSourceConfig.getInitialSize());
        druidDataSource.setMinIdle(dataSourceConfig.getMinIdle());

        druidDataSource.setMaxActive(Integer.valueOf(dataSourceConfig.getMaxActive()));
        druidDataSource.setMaxWait(dataSourceConfig.getMaxWait());
        druidDataSource.setTimeBetweenEvictionRunsMillis(dataSourceConfig.getTimeBetweenEvictionRunsMillis());
        druidDataSource.setMinEvictableIdleTimeMillis(dataSourceConfig.getMinEvictableIdleTimeMillis());
        druidDataSource.setValidationQuery(dataSourceConfig.getValidationQuery());
        druidDataSource.setTestWhileIdle(dataSourceConfig.isTestWhileIdle());
        druidDataSource.setTestOnBorrow(dataSourceConfig.isTestOnBorrow());
        druidDataSource.setTestOnReturn(dataSourceConfig.isTestOnReturn());
        druidDataSource.setPoolPreparedStatements(dataSourceConfig.isPoolPreparedStatements());
        druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(dataSourceConfig.getMaxPoolPreparedStatementPerConnectionSize());

        try {
            druidDataSource.setFilters(dataSourceConfig.getFilters());
        } catch (SQLException e) {
            log.error("druid datasource configuration initialize failed", e);
        }

        log.info("druid datasource configuration initialize succeeded");
        return druidDataSource;
    }

    @Bean(value = "sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        // TODO 不能用：getResources，它只能用单个文件，不能用通配符*
        factoryBean.setMapperLocations(resolver.getResources(DataSourceConstants.XML_LOCATION_PATTERN));
        factoryBean.setTypeAliasesPackage(DataSourceConstants.TYPE_ALIASES_PACKAGE);

        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setMapUnderscoreToCamelCase(true);
        factoryBean.setConfiguration(configuration);

        // 注：要在这里加载分页插件，否则不生效
        Interceptor[] plugins = {mybatisPlusInterceptor};
        factoryBean.setPlugins(plugins);

        return factoryBean.getObject();
    }

    @Bean(value = "transactionManager")
    @Primary
    public DataSourceTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(value = "sqlSessionTemplate")
    @Primary
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
